﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;

namespace CabConfigProvisioning
{
    public class CabExtract : FdiFunctions
    {
        protected readonly Dictionary<string,DecompressedFile> _resources;
        protected FileInfo _cabFile;
        protected Regex _pathPattern;
		protected bool _bProcessMatching;
        
        public CabExtract(FileInfo cabFile)
        {
            SetDelegates();
            CreateContext();
            _resources = new Dictionary<string,DecompressedFile>();
            _cabFile = cabFile;
        }

        public bool IsCabinet(out FdiCabinetInfo cabinfo)
        {
            if (_bDisposed)
                throw new ObjectDisposedException("Context has already been disposed!");

            using (GcFile cab = new GcFile(_cabFile))
            {
                var info = new FdiCabinetInfo();
                var ret = FdiIsCabinet(_hContext, cab.Handle, info);
                cabinfo = info;
                return ret;
            }
        }

        public string PathPattern
        {
            set { _pathPattern = new Regex(value, RegexOptions.IgnoreCase); }
        }

        protected virtual bool Matches(string sResourceName)
        {
            return _pathPattern != null && _pathPattern.IsMatch(sResourceName);
        }

        protected virtual bool ProcessFile(DecompressedFile file)
        {
            return true;
        }

        private IntPtr OutputFileOpen(FdiNotification fdin)
        {
            DecompressedFile file;
            if(_resources.TryGetValue(fdin.psz1, out file))
            {
                file.Handle = GcFile.CreateNew();
                return file.Handle;
            }

            return Matches(fdin.psz1) ? GcFile.CreateNew() : IntPtr.Zero;  // skip extraction
        }

        protected virtual IntPtr OutputFileClose(FdiNotification fdin)
        {
            DecompressedFile file;
            if (_resources.TryGetValue(fdin.psz1, out file))
            {
                file.Found = true;
                file.Content = GcFile.Close(fdin.hf);
                if (!ProcessFile(file))
                    return (IntPtr)0; // failure
            }
            else
            {
                if (Matches(fdin.psz1))
                {
                    file = new DecompressedFile(fdin.psz1);
                    file.Content = GcFile.Close(fdin.hf);
                    if (_bProcessMatching && !ProcessFile(file))
                        return (IntPtr)0; // failure
                }
            }

            return (IntPtr)1;   // success
        }

        private IntPtr NotifyCallback(FdiNotificationType fdint, FdiNotification fdin)
        {
            switch (fdint)
            {
                case FdiNotificationType.CopyFile:
                    return OutputFileOpen(fdin);
                case FdiNotificationType.CloseFileInfo:
                    return OutputFileClose(fdin);
                default:
                    return IntPtr.Zero;
            }
        }

        public void Clear()
        {
            _resources.Clear();
        }

        public void Add(String sResource)
        {
            _resources[sResource] = new DecompressedFile(sResource);
        }

        public void Add(params String[] saResource)
        {
            foreach(String sResource in saResource)
                _resources[sResource] = new DecompressedFile(sResource);
        }

        public byte[] this[string sResource]
        {
            get
            {
                DecompressedFile file = _resources[sResource];
                if (file.Found)
                    return file.Content;
                else
                    throw new FileNotFoundException(sResource);
            }
        }

        public bool Contains(string sResource)
        {
            DecompressedFile file;
            bool bContains = _resources.TryGetValue(sResource, out file) ? file.Found : false;
            return bContains;
        }

        public virtual void Execute()
        {
            if (_bDisposed)
                throw new ObjectDisposedException("Context has already been disposed!");

            String sDir;
            String sName;
            FxiHelpers.SplitFilePath(_cabFile, out sDir, out sName);
            if(!FdiCopy(_hContext, sName, sDir, 0, NotifyCallback, IntPtr.Zero, IntPtr.Zero))
                throw new IOException("Error during extracting resources from cabinet file!");
        }

    }
}
